//
// SoapTrace.Net - A soap message monitoring utility. 
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using System;
using WinPcap;

namespace SoapTrace.Core.Communication
{
	/// <summary>
	/// Provides the abstract base class for a Soap message packet monitor.
	/// </summary>
	public abstract class SoapPacketMonitorBase
	{
		/// <summary>
		/// Soap request received event.
		/// </summary>
		public event SoapRequestMessageReceivedEventHandler SoapRequestMessageReceived;

		/// <summary>
		/// Soap response received event.
		/// </summary>
		public event SoapResponseMessageReceivedEventHandler SoapResponseMessageReceived;
		
		/// <summary>
		/// Packet received event.
		/// </summary>
		public event PacketReceivedEventHandler PacketReceived;
		
		/// <summary>
		/// IP packet received event.
		/// </summary>
		public event PacketReceivedEventHandler IPPacketReceived;		
		
		/// <summary>
		/// The packet monitor.
		/// </summary>
		private TcpPacketMonitor m_Monitor = new TcpPacketMonitor( );
		
		/// <summary>
		/// The monitored clients.
		/// </summary>
		private SoapPacketClientCollection m_Clients = new SoapPacketClientCollection( );		
		
		/// <summary>
		/// Monitored clients where we did not receive a connection request.
		/// </summary>
		private SoapPacketClientCollection m_MissedConnectionClients = new SoapPacketClientCollection( );
		
		/// <summary>
		/// Flag that enables the capturing of tcp packets when we missed
		/// the connection.
		/// </summary>
		private bool m_MissedConnectionCaptureEnabled;
		
		/// <summary>
		/// Handler for tcp packet bytes that we missed the initial connection.
		/// </summary>
		private TcpCommunicationEventHandler m_MissedConnectionBytesTransferredHandler = null;
		
		/// <summary>
		/// Creates a new instance of the <see cref="SoapPacketMonitorBase"/>
		/// class.
		/// </summary>
		public SoapPacketMonitorBase( )
		{
			m_Monitor.ClientConnected += new TcpCommunicationEventHandler( ClientConnected );
			m_Monitor.ClientDisconnected += new TcpCommunicationEventHandler( ClientDisconnected );
			m_Monitor.BytesTransferred += new TcpCommunicationEventHandler( BytesTransferred );
		}
		
		/// <summary>
		/// Sets the message filter.
		/// </summary>
		/// <param name="address">The IP address of the remote server.</param>
		/// <param name="port">The port of the remote server.</param>
		public void SetFilter( string address, int port )
		{
			if( m_Monitor.IsFilterMatch( address, port ) )
			{
				// No filter change.
			}
			else
			{
				ClearClients( );
				m_Monitor.SetFilter( address, port );
			}
		}
		
		/// <summary>
		/// Clears all the clients currently connected.
		/// </summary>
		public void ClearClients( )
		{
			m_Clients.Clear( );
			m_MissedConnectionClients.Clear( );
			m_Monitor.Clear( );
		}
		
		/// <summary>
		/// Gets or sets whether the data from tcp packets will be 
		/// read if the connection was missed.
		/// </summary>
		public bool MissedConnectionCaptureEnabled
		{
			get
			{
				return MissedConnectionCaptureEnabled;
			}
			
			set
			{
				m_MissedConnectionCaptureEnabled = value;
				
				if( value )
				{
					if( m_MissedConnectionBytesTransferredHandler == null )
					{
						m_MissedConnectionBytesTransferredHandler = new TcpCommunicationEventHandler( MissedConnectionBytesTransferred );
						m_Monitor.MissedConnectionBytesTransferred += m_MissedConnectionBytesTransferredHandler;
					}
				}
				else
				{
					if( m_MissedConnectionBytesTransferredHandler != null )
					{
						m_Monitor.MissedConnectionBytesTransferred -= m_MissedConnectionBytesTransferredHandler;
					}
				}
			}
		}
		
		/// <summary>
		/// Processes a packet.
		/// </summary>
		/// <param name="packet">A data packet.</param>
		protected void ProcessPacket( Packet packet )
		{
			OnPacketReceived( packet );
			
			m_Monitor.ProcessPacket( packet );
		}
		
		/// <summary>
		/// Processes an IP packet.
		/// </summary>
		/// <param name="packet">A data packet.</param>
		protected void ProcessIPPacket( IPPacket packet )
		{
			OnIPPacketReceived( packet );
			
			m_Monitor.ProcessIPPacket( packet );
		}		
		
		/// <summary>
		/// Soap message response received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapResponseReceivedEventArgs"/> object.</param>
		protected virtual void OnSoapResponseReceived( object sender, SoapResponseReceivedEventArgs e )
		{
			if( SoapResponseMessageReceived != null )
			{
				SoapResponseMessageReceived( sender, e );
			}		
		}

		/// <summary>
		/// Soap message request received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapRequestReceivedEventArgs"/> object.</param>
		protected virtual void OnSoapRequestReceived( object sender, SoapRequestReceivedEventArgs e )
		{
			if( SoapRequestMessageReceived != null )
			{
				SoapRequestMessageReceived( sender, e );
			}
		}	
		
		/// <summary>
		/// Raises the <see cref="PacketReceived"/> event.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">Event argument that contains the packet received.
		/// </param>
		protected void OnPacketReceived( object sender, PacketReceivedEventArgs e )
		{
			if( PacketReceived != null )
			{
				PacketReceived( this, e );
			}
		}
		
		/// <summary>
		/// Raises the <see cref="IPPacketReceived"/> event.
		/// </summary>
		/// <param name="packet">The IP packet.</param>
		protected void OnIPPacketReceived( IPPacket packet )
		{
			if( IPPacketReceived != null )
			{
				PacketReceivedEventArgs EventArgs = 
					new PacketReceivedEventArgs( packet );
				
				IPPacketReceived( this, EventArgs );
			}
		}
		
		/// <summary>
		/// Raises the <see cref="PacketReceived"/> event.
		/// </summary>
		private void OnPacketReceived( Packet packet )
		{
			if( PacketReceived != null )
			{
				PacketReceivedEventArgs EventArgs = 
					new PacketReceivedEventArgs( packet );
				
				OnPacketReceived( this, EventArgs );
			}
		}
		
		/// <summary>
		/// Handles the bytes transferred event.
		/// </summary>
		protected virtual void BytesTransferred( object sender, TcpCommunicationEventArgs e )
		{			
			if( IsFromClient( e ) )
			{
				ProcessRequest( e.SourceAddress.ToString( ), e.GetBytes( ) );
			}
			else if( IsFromServer( e ) )
			{
				ProcessResponse( e.DestinationAddress.ToString( ), e.GetBytes( ) );
			}
		}
		
		/// <summary>
		/// Handles the missed connection bytes transferred event.
		/// </summary>
		protected virtual void MissedConnectionBytesTransferred( object sender, TcpCommunicationEventArgs e )
		{			
			if( IsFromClient( e ) )
			{
				ProcessMissedConnectionRequest( e.SourceAddress.ToString( ), e.GetBytes( ) );
			}
			else if( IsFromServer( e ) )
			{
				ProcessMissedConnectionResponse( e.DestinationAddress.ToString( ), e.GetBytes( ) );
			}
		}
		
		/// <summary>
		/// New client connected event handler.
		/// </summary>
		protected virtual void ClientConnected( object sender, TcpCommunicationEventArgs e )
		{
			// Create new client.
				
			SoapPacketClient Client = new SoapPacketClient( );
				
			Client.LocalEndPoint = e.SourceAddress.ToString( );

			m_Clients.Add( Client );
		}		
		
		/// <summary>
		/// Client disconnected event handler.
		/// </summary>
		protected virtual void ClientDisconnected( object sender, TcpCommunicationEventArgs e )
		{
			SoapPacketClient Client = null;
			
			if( IsFromClient( e ) )
			{
				Client = m_Clients.Find( e.SourceAddress.ToString( ) );
			}
			else
			{
				Client = m_Clients.Find( e.DestinationAddress.ToString( ) );
			}
			
			if( Client != null )
			{
				m_Clients.Remove( Client );
			}
		}	
		
		/// <summary>
		/// Determines whether the IP address and port indicates that the
		/// bytes are from the client.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		/// <returns><see langword="true"/> if the address represents
		/// the client; otherwise <see langword="false"/></returns>
		private bool IsFromClient( TcpCommunicationEventArgs e )
		{
			bool IsFromClient = false;
			
			if( e.DestinationAddress.ToString( ) == m_Monitor.RemoteEndPointFilter )
			{
				IsFromClient = true;
			}
			
			return IsFromClient;
		}
		
		/// <summary>
		/// Determines whether the IP address and port of the data received
		/// indicates that the bytes are from the server.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		/// <returns><see langword="true"/> if the address represents
		/// the client; otherwise <see langword="false"/></returns>
		private bool IsFromServer( TcpCommunicationEventArgs e )
		{
			bool IsFromServer = false;
			
			if( e.SourceAddress.ToString( ) == m_Monitor.RemoteEndPointFilter )
			{
				IsFromServer = true;
			}
			
			return IsFromServer;
		}		
		
		/// <summary>
		/// Processes the soap response bytes.
		/// </summary>
		/// <param name="address">The IP address and port of the
		/// client.</param>
		/// <param name="bytes">The response bytes.</param>
		private void ProcessResponse( string address, byte[ ] bytes )
		{
			SoapPacketClient Client = m_Clients.Find( address );
			
			if( Client != null )
			{                                   
				for( int i = 0; i < bytes.Length; ++i )
				{
					if( Client.SoapResponse.AddByte( bytes[ i ] ) )
					{
						Client.SoapResponse.TimeReceived = DateTime.Now;
						Client.SoapResponse.Source = address;
						
						OnSoapResponseReceived( this, new SoapResponseReceivedEventArgs( Client.Id, Client.SoapResponse ) );
						
						Client.SoapResponse = new SoapResponse( );
					}
				}
			}
		}
		
		/// <summary>
		/// Processes a soap request in a tcp packet.
		/// </summary>
		/// <param name="address">The IP address and port of the
		/// client.</param>
		/// <param name="bytes">The request bytes.</param>
		private void ProcessRequest( string address, byte[ ] bytes )
		{
			// Get a client.
			
			SoapPacketClient Client = m_Clients.Find( address );
			
			if( Client != null )
			{				
				for( int i = 0; i < bytes.Length; ++i )
				{
					if( Client.SoapRequest.AddByte( bytes[ i ] ) )
					{
						Client.SoapRequest.TimeReceived = DateTime.Now;
						Client.SoapRequest.Source = address;
						
						OnSoapRequestReceived( this, new SoapRequestReceivedEventArgs( Client.Id, Client.SoapRequest ) );
						
						Client.SoapRequest = new SoapRequest( );
					}
				}
			}
		}	
		
		/// <summary>
		/// Processes a soap request in a tcp packet.
		/// </summary>
		/// <param name="address">The IP address and port of the
		/// client.</param>
		/// <param name="bytes">The request bytes.</param>
		private void ProcessMissedConnectionRequest( string address, byte[ ] bytes )
		{
			// Get a client.
			
			SoapPacketClient Client = m_MissedConnectionClients.Find( address );
						
			if( Client == null )
			{
				Client = new SoapPacketClient( );
				Client.SoapRequest = null;
				Client.SoapResponse = null;
				Client.LocalEndPoint = address;
				
				m_MissedConnectionClients.Add( Client );
			}
			
			if( Client.SoapRequest == null )
			{
				// Waiting for first request.
				
				if( SoapHeader.IsHttpStartLine( bytes ) )
				{
					// Found it.
					
					Client.SoapRequest = new SoapRequest( );
					Client.SoapResponse = new SoapResponse( );
					
					ProcessRequestBytes( Client, bytes );
				}
			}
			else
			{
				ProcessRequestBytes( Client, bytes );
			}
		}		
		
		/// <summary>
		/// Adds the bytes to the soap request until a complete request is 
		/// found.
		/// </summary>
		/// <param name="client">The client holding the request being
		/// built.</param>
		/// <param name="bytes">The request bytes.</param>
		private void ProcessRequestBytes( SoapPacketClient client, byte[ ] bytes )
		{
			for( int i = 0; i < bytes.Length; ++i )
			{
				if( client.SoapRequest.AddByte( bytes[ i ] ) )
				{
					client.SoapRequest.TimeReceived = DateTime.Now;
					client.SoapRequest.Source = client.LocalEndPoint;
					
					OnSoapRequestReceived( this, new SoapRequestReceivedEventArgs( client.Id, client.SoapRequest ) );
					
					client.SoapRequest = new SoapRequest( );
				}
			}
		}
		
		/// <summary>
		/// Processes a soap response in a tcp packet.
		/// </summary>
		/// <param name="address">The IP address and port of the
		/// client.</param>
		/// <param name="bytes">The response bytes.</param>
		private void ProcessMissedConnectionResponse( string address, byte[ ] bytes )
		{
			// Get a client.
			
			SoapPacketClient Client = m_MissedConnectionClients.Find( address );
		
			if( Client != null )
			{
				if( Client.SoapRequest != null )
				{						
					ProcessResponseBytes( Client, bytes );
				}
			}
		}	
		
		/// <summary>
		/// Adds the bytes to the soap response until a complete response is 
		/// found.
		/// </summary>
		/// <param name="client">The client holding the response being
		/// built.</param>
		/// <param name="bytes">The response bytes.</param>
		private void ProcessResponseBytes( SoapPacketClient client, byte[ ] bytes )
		{
			for( int i = 0; i < bytes.Length; ++i )
			{
				if( client.SoapResponse.AddByte( bytes[ i ] ) )
				{
					client.SoapResponse.TimeReceived = DateTime.Now;
					client.SoapResponse.Source = client.LocalEndPoint;
					
					OnSoapResponseReceived( this, new SoapResponseReceivedEventArgs( client.Id, client.SoapResponse ) );
					
					client.SoapResponse = new SoapResponse( );
				}
			}
		}		
	}
}
